﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using RegeditNavigator.GlobalKeyboardHook;
using RegeditNavigator.GlobalKeyboardHook.WinAPI;
using RegeditNavigator.SingleInstance;

namespace RegeditNavigator
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        private readonly KeyboardHookListener keyboardListener;
        private bool keyboardSequenceInitiated;

        public MainWindow()
        {
            InitializeComponent();

            keyboardListener = new KeyboardHookListener(new GlobalHooker());
            keyboardListener.KeyDown += OnGlobalKeyDown;
            
            // Do not start global keyboard listener if debugger is attached. It slows down the debugging.
            if (Debugger.IsAttached == false) keyboardListener.Start();
        }

        void OnGlobalKeyDown(object sender, KeyEventArgs e)
        {
            if (keyboardSequenceInitiated && e.Key == Key.C &&
                System.Windows.Input.Keyboard.Modifiers == ModifierKeys.None)
            {
                var path = ExtractClipboardText();
                if (IsValidRegeditPath(path)) Navigate(path);
            }
            else if (e.Key == Key.C && (System.Windows.Input.Keyboard.Modifiers == ModifierKeys.Control))
            {
                keyboardSequenceInitiated = true;
            }
            else keyboardSequenceInitiated = false;
        }

        private string ExtractClipboardText()
        {
            return Clipboard.ContainsText() ? Clipboard.GetText().Trim() : null;
        }

        private void Navigate(string path)
        {
            Dispatcher.BeginInvoke((Action)(() =>
                {
                    Path.Text = path;
                    Go(null, null);
                }));
        }

        private void ClearStatus()
        {
            StatusBar.Visibility = Visibility.Collapsed;
            StatusText.ToolTip = StatusText.Text = string.Empty;
            StatusIcon.Source = null;
        }

        private void SetStatus(Icon icon, string text)
        {
            StatusText.ToolTip = StatusText.Text = text;
            StatusIcon.Source = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty,
                                                                    BitmapSizeOptions.FromEmptyOptions());
            StatusBar.Visibility = Visibility.Visible;
        }

        private void Go(object sender, RoutedEventArgs e)
        {
            ClearStatus();

            var process = FindOrCreateRegeditProcess();
            if (process == null) return;

            var element = AutomationElement.FromHandle(process.MainWindowHandle);
            var treeView = element.FindFirst(TreeScope.Children,
                              new PropertyCondition(AutomationElement.ClassNameProperty, "SysTreeView32"));
            var listView = element.FindFirst(TreeScope.Children,
                              new PropertyCondition(AutomationElement.ClassNameProperty, "SysListView32"));

            var walker = new TreeWalker(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TreeItem));

            var listWalker = new TreeWalker(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem));

            // the first node is a computer
            var node = walker.GetFirstChild(treeView);

            var names = Path.Text.Split('\\');

            if (names.Any()) names[0] = Expand(names[0]);

            foreach (var name in names)
            {
                if (!ExpandOrSelectNode(node)) break;

                var parent = node;
                node = walker.GetFirstChild(node);

                while (node != null)
                {
                    if (name.Equals(node.GetCurrentPropertyValue(AutomationElement.NameProperty) as string,
                                    StringComparison.InvariantCultureIgnoreCase))
                        break;
                    node = walker.GetNextSibling(node);
                }
                if (node != null) continue;

                if (parent != null)
                {
                    var listItem = listWalker.GetFirstChild(listView);
                    while (listItem != null)
                    {
                        if (name.Equals(listItem.GetCurrentPropertyValue(AutomationElement.NameProperty) as string,
                                        StringComparison.InvariantCultureIgnoreCase))
                            break;
                        listItem = listWalker.GetNextSibling(listItem);
                    }
                    if (listItem != null) SelectListItem(listItem);
                    else ExpandOrSelectNode(parent);
                }
                break;
            }
            if (node != null) ExpandOrSelectNode(node, true);
        }

        private void SelectListItem(AutomationElement listItem)
        {
            object pattern;
            if (listItem.TryGetCurrentPattern(SelectionItemPattern.Pattern, out pattern))
            {
                var sp = (SelectionItemPattern)pattern;
                sp.Select();
            }

            listItem.SetFocus();
        }

        private Process FindOrCreateRegeditProcess()
        {
            var process = Process.GetProcessesByName("regedit").FirstOrDefault();
            if (process != null) return process;

            try
            {
                SetStatus(SystemIcons.Information, "Starting regedit process...");
                process = Process.Start(new ProcessStartInfo("regedit") { WorkingDirectory = Environment.SystemDirectory });
                process.WaitForInputIdle(5000);
            }
            catch
            {
                process = null;
                SetStatus(SystemIcons.Error, "Unable to find or start regedit process.");
            }

            if (process != null) ClearStatus();

            return process;
        }

        private static bool ExpandOrSelectNode(AutomationElement node, bool setFocus = false)
        {
            if (node == null) return false;

            object pattern;
            if (node.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out pattern) == false) return false;
            var ecp = (ExpandCollapsePattern)pattern;
            if (ecp.Current.ExpandCollapseState == ExpandCollapseState.Collapsed)
                ecp.Expand();
            if (setFocus || ecp.Current.ExpandCollapseState == ExpandCollapseState.LeafNode)
                node.SetFocus();

            return true;
        }

        private static readonly Dictionary<string, string> RegeditPathPrefix = new Dictionary<string, string>
            {
                { "HKCR", "HKEY_CLASSES_ROOT" },
                { "HKCU", "HKEY_CURRENT_USER" },
                { "HKLM", "HKEY_LOCAL_MACHINE" },
                { "HKU", "HKEY_USERS" },
                { "HKCC", "HKEY_CURRENT_CONFIG" },
                { "HKPD", "HKEY_PERFORMANCE_DATA" },
            };

        private bool isUserInitiatedClosing;

        private static bool IsValidRegeditPath(string text)
        {
            return RegeditPathPrefix.Keys.Any(text.StartsWith)
                   || RegeditPathPrefix.Values.Any(text.StartsWith);
        }

        private static string Expand(string name)
        {
            string result;
            return RegeditPathPrefix.TryGetValue(name, out result) ? result : name;
        }

        private static string LoadRegeditLastKey(string defaultValue)
        {
            return Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", "LastKey", null) as string ?? defaultValue;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            MinWidth = 300;
            MinHeight = ActualHeight;
            MaxHeight = ActualHeight;

            AppSingleton.WriteMainWindowHandle(new WindowInteropHelper(this).Handle);

            var source = (HwndSource)PresentationSource.FromDependencyObject(this);
            if (source != null) source.AddHook(WindowProc);

            Path.Text = LoadRegeditLastKey(Path.Text);

            Path.Focus();
            Path.SelectAll();
        }

        private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
        {
            switch (msg)
            {
                case 0x11:
                case 0x16:
                    isUserInitiatedClosing = false;
                    break;
                case 0x112:
                    if ((((int)wparam) & 0xfff0) == 0xf060)
                        isUserInitiatedClosing = true;
                    break;
            }
            
            return IntPtr.Zero;
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            if (isUserInitiatedClosing) e.Cancel = true;
            Hide();
            isUserInitiatedClosing = false;
        }

        private void Show(object sender, RoutedEventArgs e)
        {
            Show();
        }

        private void Exit(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}
